iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
Software Development

30天打造一套企業PLM系列 第 13

Day 13:版本生命週期與改版機制

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260830/20161290QVUj5Zrb6G.jpg

系列:30 天打造企業級 PLM|面向:後端

問題場景

三年前出貨的產品出問題,品保要調閱「當時那一版」的規格與 BOM。版本管理的價值,在事故發生那天才真正顯現。Day 3 定了主檔薄、版本厚、版本不可變的模型,今天講它跑起來的機制:改版不是改資料,是長出一個新版本;而且從草稿到發行的整段路,正式資料一個字都不能被動到。

品項頁實機畫面(版本狀態、生命週期與各分頁):

https://ithelp.ithome.com.tw/upload/images/20260830/201612907Zodteg3Wr.png

商業邏輯設計

  • 什麼改動需要改版?影響 fit / form / function 才升版,改個描述打錯字不用。這條線是業務畫的,系統提供機制、不下判斷
  • 改版必須走表單(ECO)。改版牽動成本重算、供應鏈通知、庫存處置,必須有人簽字,所以系統不開放直接對 Released 版本動手,唯一的路是開變更單
  • Obsolete 不是刪除。停產件的售後備料、追溯查詢都還要用,狀態機單向前進(Draft → Released → Obsolete),沒有刪版本這個動作

技術選型與取捨

架構演進:從 Agile 變更鎖定黑箱到實體隔離預備版本

在 Oracle Agile PLM 中,版本變更(ECO Affected Items)是由後端 EJB 核心深度管轄的。

當料號被掛入變更單時,Agile 會在後端產生一個 Pending Revision,並對該物件施加強烈的「變更鎖定(Change Lock)」。這套舊架構有著經典痛點:

  1. 幽靈鎖定與狀態殘留:簽核過程中若發生流程異常或管理員強行取消表單,EJB 容器常常未能乾淨釋放鎖定或清理 Pending 狀態,導致料號陷入「被幽靈變更單卡住」的狀態,再也無法掛入新表單。
  2. 上下文深度依賴:在舊架構下,同一個料號的「已發行資料」與「簽核中暫存」混在同一套核心表裡,查詢時必須強制帶入 ECO 上下文才能分流。

Mini-PLM 採用實體隔離的預備版本(Prepared Revision)模式:以乾淨的兩個 JPA 實體徹底隔開「正式世界」與「簽核平行世界」,放行時單一交易原子合併,架構純粹且無殘留鎖定風險。

預備版本(prepared revision):簽核中的平行世界

Day 3 講過 FormItemLink.pending_data 暫存簽核中的欄位修改;版本這端的對應機制是預備版本:品項掛進變更單時,先從目前版本長出一個 Draft 狀態的新版本(實碼節錄,FormItemLinkService):

// 若指定版號已存在且不是原版號,直接回 409,避免後續拋 500
if (!isTemporaryRevisionNumber(newRevNumber)
        && itemRevisionRepository.findByItemAndRevisionNumber(item, newRevNumber).isPresent()) {
    throw new ConflictException("版本號已存在: " + newRevNumber);
}
ItemRevision preparedRevision = itemRevisionService.createPreparedRevisionForForm(
        revision, newRevNumber,
        normalizeText(request.getTargetLifecyclePhase()),
        revision.getDescription(), currentUser, form.getFormNumber());
link.setNewRevision(preparedRevision);

於是簽核期間同時存在兩個世界。正式世界是 is_latest 的 Released 版本,所有人查到的都是它;平行世界是掛在 FormItemLink 上的 Draft 預備版本,只在變更單的上下文可見。簽核中的所有編輯,欄位、BOM redline、AML redline,都作用在預備版本上。表單放行那一刻,預備版本轉正(Released、is_latest 換手、原版本卸下 latest);駁回則整個平行世界丟棄。

**「簽核中」與「已生效」的隔離不是用旗標,是用兩個實體。**這比同一列資料加個 pending 欄位乾淨得多,因為兩個世界各自有完整的欄位、BOM 與附件。

版本族譜與流水號

base_revision_id 自參照記錄這版從哪版長出來,族譜靠結構不靠 log。版號策略:使用者可指定(A、B、C…),未指定時先給暫時版號、放行時定案。指定版號撞號在掛單當下就擋 409,fail fast,不是等放行才炸 500。

核心內容:放行的原子時刻

表單放行時,每個 Affected Item 要在一個交易裡完成六件事:

1. pending_data 落入預備版本欄位
2. 預備版本 BOM / AML redline 定案
3. 預備版本 → RELEASED、蓋 release_date
4. is_latest 換手(舊版卸下、新版掛上)
5. mp_item.latest_revision_id 更新(查詢捷徑同步)
6. 依 targetLifecyclePhase 設定生命週期階段

一致性約束跟 Day 9 的 returnToPending 同款,清單少一條就出鬼。latest_revision_id 是為查詢效能加的反正規化捷徑,列表頁不用每次 MAX() 找最新版,代價就是第 5 步永遠不能漏。反正規化的規矩向來如此:加捷徑的人,要負責所有寫入路徑的同步。

踩坑記錄:孤兒預備版本

預備版本機制的邊角案例比主流程多得多。真實案發:品項從變更單移除(link 變 REMOVED)時,早期版本沒有清掉掛在 link 上的預備版本。這個 Draft 版本成了孤兒,佔著版號。使用者下次把同一顆料掛進新變更單、指定同一個版號:409 版本號已存在。「明明沒有 B 版,為什麼說 B 版存在?」因為有一個看不見的 B 版孤兒。

修法除了補上移除時清理,還加了一道防禦性自我修復(實碼):

// 加入前先修復歷史殘留(REMOVED 關聯殘留 newRevision、孤兒預備版本)
repairResidualPreparedRevisionsBeforeAdd(item);

每次掛品項進表單前,先掃一遍該品項的歷史殘留並修復。這是企業系統的務實選擇:bug 修好了,但已經在生產資料庫裡的髒資料不會自己消失。與其寫一次性清理 SQL 賭它掃得乾淨,不如把修復邏輯內建在下一次操作的入口,讓系統自我痊癒。

小結

改版機制的核心是平行世界:預備版本隔離簽核中與已生效,放行是原子的世界合併,反正規化捷徑人人有責,孤兒資料靠自我修復收拾。版本講完了,明天講掛在版本上的那棵樹。Day 14:BOM 多階展開,後端演算法與前端樹狀表格。


上一篇
Day 12:Trigger 規則引擎(下)——Spring 交易傳播實戰
下一篇
Day 14:BOM 多階展開——後端演算法+前端樹狀表格
系列文
30天打造一套企業PLM17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言